Strict
framework BRL.GLMax2D
Import BaH.Bass
Import BRL.D3D7Max2D
import BRL.Random
import Pub.Glew
import BRL.Standardio
import brl.pngloader

'?Win32
'Import "projicon.o" 'Icon file 74kB
'?

'Include "TexturedPolyInc.bmx"
include "glowing_sokurah_line.bmx"


apptitle = "magnetic music viz by Torb"

Global channel:TBassChannel
initBass()

SetGraphicsDriver GLMax2DDriver()
Graphics 1024,768,0',0,60 'dont need to setup projection

'TPoly.Initialise()
'TPoly._Begin()
Global Image:TImage = LoadImage("dot.png")
'Global GlowingDot:TImage = makeImg()'LoadImage ("dot.png")
MidHandleImage Image

Global xres# = Float(GraphicsWidth())
Global yres# = Float(GraphicsHeight())

Global nx#=0, ny#=0, sx#=0, sy#=0; 'Pole positions

Local nump:Int=8192/2
Global px#[NUMP]
Global py#[NUMP]
Global otim#;

Global MAXVERTS:Int = 2*nump 'line start & end
Global vertices#[2*MAXVERTS] '2 floats xy

For Local i:Int=0 Until NUMP
	px[i] = xres*RndFloat();
	py[i] = yres*RndFloat();
Next
otim = MilliSecs() / 1000.0

'place poles
nx = xres*0.75; 
ny = yres*(0.5-.001);
sx = xres*0.75; 
sy = yres*(0.5+.001);

Const RADTODEG# = 180.0/Pi
global err:int = 0


SetMaskColor(255,255,255)
'Global pixmap:TPixmap = CreatePixmap(16,16,PF_RGBA8888)
'DrawCircleGradient(pixmap)
'Global particleImg:TGLImageFrame = TGLImageFrame.CreateFromPixmap(pixmap,FILTEREDIMAGE)
'Global texture_name:Int = particleImg.name
Global pointplots:Int = 0

'initPointSprite()
Global fpsCounter:TFPSCounter = New TFPSCounter

Local _left:Int, _right:Int

global vis:TVisualizer = new TVisualizer
global bassBeat# = 0
global midBeat# = 0

While Not AppTerminate()	
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )

	If MouseDown(2)
	   nx = MouseX();
	   ny = MouseY();
	EndIf
	
	If MouseDown(1)
	   sx = MouseX()
	   sy = MouseY()
	EndIf
	


	Local mx# = nx-sx; 
	Local my# = ny-sy; 
	Local cx# = (nx+sx)/2.0; 'centerpoint, between north & south.
	Local cy# = (ny+sy)/2.0;
	Local f# = 1.0/Sqr(mx*mx+my*my); '1/distance north<->south
	mx = mx*f; 
	my = my*f;
   
   	Local hx# = xres*0.5; 
	Local hy# = yres*0.5; 'middle of screen
   	Local tim# = MilliSecs()/1000.0; 
	Local dt# = tim-otim;
	otim = tim;
   
'	Case ALPHABLEND
'			glEnable GL_BLEND
'			glBlendFunc GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA
'			glDisable GL_ALPHA_TEST
'		Case LIGHTBLEND
'			glEnable GL_BLEND
'			glBlendFunc GL_SRC_ALPHA,GL_ONE
'			glDisable GL_ALPHA_TEST
	'SetBlend(LIGHTBLEND)
	'glEnable GL_BLEND
	'glBlendFunc GL_SRC_ALPHA,GL_ONE
	'glDisable GL_ALPHA_TEST 'Important! GL turns this on for text methinks. 
	
	'glBindTexture(GL_TEXTURE_2D, texture_name);
	'glPointSize 16.0



		
	'glDisable(GL_TEXTURE_2D)
	'glDisable(GL_BLEND)
	'glBegin GL_LINES
	'glBegin GL_POINTS
	
	'TPoly._Begin()
	
	SetAlpha 1.0
	SetBlend LIGHTBLEND
	'glBlendFunc GL_SRC_ALPHA,GL_ONE
	
	_left :* 0.8
	_right:* 0.8
		
	Local newLeft:Int, newRight:Int
	channel.GetLevel(newLeft, newRight)
	vis.update(channel)
	newLeft :/ 2500.0
	newRight :/ 2500.0
	_left :+ newLeft 
	_right :+ newRight 

	Local mousxpos# = Mousex() / xres	
	Local mousypos# = MouseY() / yres
	
	Local alpha:Int = bassBeat/5.0 'mousxpos*255	
	
	Local indice:Int = 0
   	For Local i:Int=0 Until NUMP
		Local x# = px[i]; 
		Local y# = py[i];
		Local sd# = (x-sx)*(x-sx) + (y-sy)*(y-sy); 'distance to south pole
	      
		Local j# = 0.0
		'Particle out of screen?
		If (Abs(x-hx) >= hx) Or (Abs(y-hy) >= hy)
		'If x < -100 Or x > xres+100 Or y < -100 Or y > yres+100
			j = (xres+yres)*2*RndFloat(); 'Select random point on border
			
			If (j < xres) Then
				x = j; 
				y = 0;
			Else If (j < xres*2)
				x = j-xres;
				y = yres; 
			Else If (j < xres*2+yres)
				x = 0; 
				y = j-xres*2; 
			Else  
				x = xres; 
				y = j-xres*2-yres; 
		      EndIf
		EndIf
	
		'Toushes south. set new pos.
		If sd < 16*16
			Local  a# = (Pi*2)*RndFloat();
			x = Cos(a*RADTODEG)*16+nx;
			y = Sin(a*RADTODEG)*16+ny;
		EndIf
	
		'north direction?
	      Local  nd# = 1.0/( (x-nx)*(x-nx) + (y-ny)*(y-ny) ); '1 over sq dist
	      sd = 1.0/( (x-sx)*(x-sx) + (y-sy)*(y-sy) );
	      Local  vx# = (x-nx)*nd - (x-sx)*sd; 'velocity at point is decidend by dist to north and south.
	      Local  vy# = (y-ny)*nd - (y-sy)*sd;
	
	      f =4.0/Sqr(vx*vx+vy*vy); 'line length
		'setcol((i%72)*64+64,(i%80)+128,64);
	      'SetColor (i Mod 72)*64+64, (i Mod 80)+128, 64
	      'glColor4ub (i Mod 72)*64+64, (i Mod 80)+128, 64, alpha
		'glvertex2f x-vx*f,y-vy*f
		'glVertex2f x+vx*f,y+vy*f
		'blockscan x-vx*f,y-vy*f,x+vx*f,y+vy*f,2
		'DrawLine(x-vx*f,y-vy*f,x+vx*f,y+vy*f);
		
		Local r:Int = (i Mod 72)*64+64
		Local g:Int =  (i Mod 80)+128
		Local b:Int = 64
		Local argb:Int = (alpha Shl 24) + r Shl 16 + g Shl 8 + b
		setalpha alpha
		'setalpha MouseX() / xres * 1
		setcolor r,g,b

	
		'TPoly.Line(GlowingDot, 0, x-vx*f,y-vy*f, argb, x+vx*f,y+vy*f, argb, 0.5+( 15+_right ) ) 'MouseX() / xres * 50
		'(x0#, y0#, x1#, y1#, width
		LineCool(  x-vx*f,y-vy*f,x+vx*f,y+vy*f,  5+( 10+_right )) 

		'vertices[indice+0] = x-vx*f
		'vertices[indice+1] = y-vy*f
		'indice :+2
		'vertices[indice+0] = x+vx*f
		'vertices[indice+1] = y+vy*f
	      'indice :+2
	
	      f = 1+(midBeat)*dt/Sqr(vx*vx+vy*vy);
	      x :+ vx*f;
	      y :+ vy*f;	
	      px[i] = x;
	      py[i] = y;
	Next
	'glEnd
	
	

	
	
	'glBegin GL_LINES
		'glVertex2f(0,0)
		'glVertex2f 50,50
	'glEnd
	'glEnable(GL_TEXTURE_2D)
	
	'TPoly._End()  'invalid enum?
	
	'?debug
	'checkError()
	'?
	
	setalpha(0.5)
	vis.drawLogSpectrumFast()
	
	SetAlpha(1.0)
	SetBlend(MASKBLEND)
	SetColor 255,0,0
	'SetScale(2,2)
	'DrawText "N",nx-3,ny-5
	'DrawText "S",sx-3,sy-5
	'SetScale(1,1)
	
	glColor3ub(255,255,255)
	SetColor 255,255,255
	DrawText "FPS: "+fpsCounter.fps, 0, 0
	'DrawText "points plotted with line algo:"+pointplots ,0,15
	'DrawText "move mouse up and down to control alpha:",0,15*2
	DrawText "left:"+_left,0,15*3
	DrawText "right:"+_right,0,15*4
	drawtext "bassbeat " + bassbeat, 0,15*5 
	drawtext "midBeat " + midBeat , 0,15*6
	pointplots = 0'reset

	Flip(0)
	'Cls
	fpsCounter.update()
Wend
TBass.Free()
End


?debug
Function checkError()
	Local checkedAllErrors:Int = False
	Local glerrorList$ = ""
	Local numErrors:Int = 0
	While checkedAllErrors=False
		Local errorCode:Int = glGetError()
		If errorCode = GL_NO_ERROR
			checkedAllErrors = True
			
		Else
			numErrors = numErrors + 1
			If errorCode = GL_INVALID_ENUM glerrorList:+" invalid enum "
			If errorCode = GL_INVALID_VALUE glerrorList:+" invalid value "
			If errorCode = GL_INVALID_OPERATION glerrorList:+" invalid operation "
			If errorCode = GL_STACK_OVERFLOW glerrorList:+" stack overflow "
			If errorCode = GL_STACK_UNDERFLOW glerrorList:+" stack underflow "
			If errorCode = GL_OUT_OF_MEMORY glerrorList:+" GL_OUT_OF_MEMORY "
			If errorCode = GL_TABLE_TOO_LARGE glerrorList:+" GL_TABLE_TOO_LARGE "
		EndIf
	Wend
	If glerrorList <> ""
		err = True
		Print "#"+numErrors +" glGetErrors this frame: "+glerrorList
	Else
		err = False
	EndIf
End Function
?

Function initPointSprite()
'Glew, opengl extension wrangler is used To se If an extension is supported.
	glewInit()	
	If(GL_POINT_SPRITE)
		Print "GL_POINT_SPRITE is supported"
		glEnable(GL_POINT_SPRITE)
	Else 
		Print "GL_POINT_SPRITE is Not supported"
		End
	EndIf
	'glEnable( GL_BLEND )
	'glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) 'additive without overblending
	'glPointSize( 30.0 )
	
	glActiveTexture(GL_TEXTURE0);
	glEnable( GL_TEXTURE_2D );
	glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE); 'Generates tex coords for Point Sprite
	'modes: http://www.khronos.org/opengles/documentation/opengles1_0/html/glTexEnv.html
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);  'GL_MODULATE, GL_DECAL, GL_BLEND, Or GL_REPLACE.
	
	'Local color:Float[] = [1.0, 0.0, 0.0]
	'glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color[0] )
	'glBindTexture(GL_TEXTURE_2D, texture_name);
End Function


'im guessing its bresenham. prolly overkill.
'taken from the evaldraw example blockscan.kc
'converted to BMax.
Function blockscan(x0#,y0#,x1#,y1#,blocksiz#)
   'Scale down to blocky pixels
   x0 :/ blocksiz
   y0 :/ blocksiz
   x1 :/ blocksiz
   y1 :/ blocksiz
   Local prec# = 65536.0 '// 2^16
   Local ix:Int = Floor(x0);
   Local ix1:Int = Floor(x1)-ix;
   Local ixi:Int = (ix1>0)*2-1;
   Local iy:Int = Floor(y0);
   Local iy1:Int = Floor(y1)-iy;
   Local iyi:Int = (iy1>0)*2-1;
   Local fx# = x0-ix;
   If (ixi > 0) fx = 1-fx;
   Local  fy# = y0-iy;
   If (iyi > 0) fy = 1-fy;
   Local gx# = Abs(x1-x0)*prec;
   Local idx:Int = Int(gx);
   Local gy# = Abs(y1-y0)*prec;
   Local idy:Int = Int(gy);

   Local id:Int;
   If (ix1 = 0) Then 
	id = -1; idx = 0;
   Else If (iy1 = 0)
	id =  0; idy = 0;
   Else
	id = Int(fy*gx - fx*gy);
   EndIf
   For Local c:Int=Abs(ix1)+Abs(iy1) Until 0 Step -1
	pointplots :+1
	glVertex2f( ix*blocksiz , iy*blocksiz ) 'scale up to screen pixels

      If (id >= 0)
		ix :+ ixi; id :- idy;
      Else 
		iy :+ iyi; id :+ idx; 
	EndIf
   Next
End Function

Function drawCircleGradientOld(px:TPixmap)
	Local siz:Float = px.width
	Local center:Float = (siz-1) / 2.0
   	For Local y:Int=0 Until siz
   	For Local x:Int=0 Until siz
     	 	Local t:Float = Sqr( (center-x)*(center-x) + (center-y)*(center-y) ) / center
		Local c:Int = 255-255*t
		If( c < 0 ) c = 0
		If( c > 255 ) c = 255
		
		Local a:Int = c 'alpha is same as color gradient
		Local argb:Int = (a Shl 24) + c Shl 16 + c Shl 8 + c
		WritePixel px,x,y,argb      	
   	Next
	Next 
End Function



Type TFPSCounter
	Field frames:Int=0
	Field fps:Int=0
	Field sec:Long = MilliSecs()
	
	Method update()
		Local timenow:Int = MilliSecs()
		If(sec < timenow )
			sec = timenow + 1000
			fps = frames
			frames = 0
		EndIf
		frames :+1
	End Method
End Type


Function makeImg:TImage ()
	Local img:TImage = CreateImage(64,64)
	Local pixmap:TPixmap = LockImage(img)
	drawCircleGradient(pixmap)
	UnlockImage(img)
	Return img
End Function

Function drawCircleGradient(px:TPixmap)
	Local siz:Float = px.width
	Local center:Float = (siz-1) / 2.0
   	For Local y:Int=0 Until siz
   	For Local x:Int=0 Until siz
     	 	Local t:Float = Sqr( (center-x)*(center-x) + (center-y)*(center-y) ) / center
		'Local c:Int = 255-(t)*255
		Local c:Int = 250-(t^0.6)*255
		If( c < 0 ) c = 0
		If( c > 255 ) c = 255
		
		Local argb:Int = ( c Shl 24) + c Shl 16 + c Shl 8 + c
		WritePixel px,x,y,argb      	
   	Next
	Next 
End Function

Function initBass()
	If Not TBass.Init(-1,44100,0,Null,Null) Then
		DebugLog "Can't initialize device : " + TBass.ErrorGetCode()
		End
	End If
	
	Local isMod:Int = False
	Local file:String = "tune.mod"
	channel= New TBassStream.CreateFile(file, 0, 0, 0)
	
	If Not channel Then
		channel = New TBassMusic.FileLoad(file, 0, 0, BASS_SAMPLE_LOOP | BASS_MUSIC_RAMPS | BASS_MUSIC_PRESCAN, 0)
		If channel Then
			isMod = True
		End If
	End If
	
	If channel Then
		Local length:Long = channel.GetLength(BASS_POS_BYTE)
	
		If Not channel.Play(False) Then
			DebugLog "can't play... : "  + TBass.ErrorGetCode()
		Else
			'play
			Local active:Int = channel.IsActive()
		EndIf
	End If
	
End Function


Type TVisualizer
	Field SPECWIDTH:Int
	Field SPECHEIGHT:Int
	Field specpos:Int = 0 'marker position for 2nd mode, visualizer
	Field numVisualizers:Int = 4
	Field visualizerNum:Int = 0
	Field fft:Float[1024] 'used by all visualizers but the scope
	Field numChannels:Int = 0
	Field buf:Float Ptr 'used by scope
	Field _left:Int, _right:Int 'left and right amplitude, expected 0-32700
	
	Method New()
		SPECWIDTH = xres
		SPECHEIGHT = Int( Float(yres)*0.75 )
	End Method
	
	Method update(channel:TBassChannel)
		Local ci:TBassChannelInfo = channel.GetInfo()
		numChannels = ci.getChannels()
		buf = Float Ptr(MemAlloc(numChannels * SPECWIDTH * 4 )) 'float is 4 bytes
		channel.getData(buf, (numChannels * SPECWIDTH * 4) | BASS_DATA_FLOAT) 'Get FFT data as float
		channel.getData(fft, BASS_DATA_FFT2048) 'get FFT data, 1024 floats
		channel.GetLevel(_left, _right)
	End Method

	Method DrawLogSpectrum()
		Cls
		SetColor 255,0,0
		Local b0:Int = 0
		Local bands:Int = 28
		For Local x:Int = 0 Until bands
			Local sum:Float = 0
			Local sc:Int
			Local b1:Int = 2^(x * 10.0 / (bands-1))
			
			If b1 > 1023 Then b1 = 1023
			If b1 <= b0 Then b1 = b0+1 'bake sure it uses at least 1 FFT bin
			sc = 10 + b1 - b0
			'b0 = 0 this destroys the bars, makes them all the same height
			While( b0 < b1 )
				
				sum :+ fft[1+b0]
				b0 :+1 ' this was a for loop, for loops do the incrementation after run thru
			Wend
			
			Local y:Int = (Sqr(sum / Log10(sc)) * 1.7 * SPECHEIGHT)-4 ' scale it
			If y > SPECHEIGHT Then y=SPECHEIGHT ' cap it
			y :-1
			
			Local barStep:Int = SPECWIDTH/BANDS
			While( y >= 0 )
				SetColor y*4,255-y*3,0
				'Plot x*(SPECWIDTH/BANDS), CANVASY - y'x*(SPECWIDTH/BANDS),y ' draw bar, no width...
				'DrawRect x*(SPECWIDTH/BANDS), CANVASY - y, (SPECWIDTH/BANDS)*0.8, y 'FAST
				
				DrawLine x*(SPECWIDTH/BANDS), yres - y, x*(SPECWIDTH/BANDS)+(SPECWIDTH/BANDS)*0.8, yres - y
				y:-1
			Wend
		Next			
	End Method
	
	Method DrawLogSpectrumFast()
		Local b0:Int = 0
		Local bands:Int = 28
		For Local x:Int = 0 Until bands
			
			Local sum:Float = 0
			Local sc:Int
			Local b1:Int = 2^(x * 10.0 / (bands-1))
			
			If b1 > 1023 Then b1 = 1023
			If b1 <= b0 Then b1 = b0+1 'make sure it uses at least 1 FFT bin
			sc = 10 + b1 - b0
			'b0 = 0 this destroys the bars, makes them all the same height
			While( b0 < b1 )
				
				sum :+ fft[1+b0]
				b0 :+1 ' this was a for loop, for loops do the incrementation after run thru
			Wend
			
			Local y:Int = (Sqr(sum / Log10(sc)) * 1.7 * SPECHEIGHT)-4 ' scale it
			If y > SPECHEIGHT Then y=SPECHEIGHT ' cap it
			y :-1
			
			Local barStep:Int = SPECWIDTH/BANDS * .75
			DrawRect barStep/2.0+x * (SPECWIDTH / BANDS), (yres-y), (SPECWIDTH / BANDS) * 0.8, y
			if x = 0 then bassBeat = y
			if x = bands/2 then midBeat = y
		Next			
	End Method
	
	Method DrawNormalSpectrum()
		'screenSurf.ClearPixels(0) '"CLS"
		Local x:Int, y:Int, y1:Int
		For x = 0 Until SPECWIDTH/2
			'Local y:Int = Sqr(fft[x+1])*3*SPECHEIGHT-4 'scale it
			y = fft[x+1]*10*SPECHEIGHT ' scale it linearly
			
			If(y>SPECHEIGHT) y=SPECHEIGHT
			'Local y1:Int
			y1 = y+y1 / 2
			
			Local p:Byte Ptr = Null
			'SetColor 0,255,0
			If(x And y1)
				y1 :- 1
				While(y1 >= 0)
					'Plot CANVASX-x,CANVASY-y1
					Local px:Int = xres-x; Local py:Int = yres-y1
					If px > 0 And px < xres And py > 0 And py < yres
						'p= PixmapPixelPtr(screenSurf,px,py)
						'p[0] = 0; p[1] = 255; p[2] = 0
					EndIf
					y1 :-1
				Wend
			EndIf
			y1 = y
			y :-1
			'SetColor 0,0,255
			While(y>=0)
				'Plot CANVASX/2-x,CANVASY-y
				If (xres/2) > 0 And (xres/2) < xres And (yres-y) > 0 And (yres-y) < yres
					'p = PixmapPixelPtr(screenSurf,CANVASX/2-x,CANVASY-y)
					'p[0] = 255; p[1] = 0; p[2] = 0
				EndIf
				y:-1
			Wend
		Next
		'DrawPixmap(screenSurf,0,0)
	End Method
End Type
